{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建神经网络模型框架 - 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Platform : win32 [win32/linux]\n",
      "Systerm  : 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)] \n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import random\n",
    "import numpy as np\n",
    "import networkx as nx\n",
    "import sys\n",
    "print('Platform : {} [win32/linux]'.format(sys.platform))  # 当前平台信息 \n",
    "print('Systerm  : {} '.format(sys.version))  # python 版本"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 创建节点类函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 节点基础类\n",
    "class Node:\n",
    "    def __init__(self, inputs=list(), name=None, is_trainable=False):\n",
    "        self.inputs = inputs   # 节点输入\n",
    "        self.outputs = []      # 节点输出\n",
    "        self.name = name       # 节点名称\n",
    "        self.is_trainable = is_trainable   # 节点参数是否可训练\n",
    "\n",
    "        # 所有输入节点的输出节点都加上当前节点。 原节点上做修改\n",
    "        for n in self.inputs:\n",
    "            n.outputs.append(self)\n",
    "\n",
    "        # 当前节点输出值\n",
    "        self.value = None\n",
    "\n",
    "        # 梯度值\n",
    "        self.gradients = {}\n",
    "\n",
    "    def forward(self):\n",
    "        pass\n",
    "\n",
    "    def backward(self):\n",
    "        pass\n",
    "\n",
    "    def __repr__(self):\n",
    "        return '{}'.format(self.name)\n",
    "    \n",
    "    \n",
    "# 占位符节点， 入度为 0\n",
    "class Placeholder(Node):\n",
    "    def __init__(self, name=None, is_trainable=False):\n",
    "        Node.__init__(self, name=name, is_trainable=is_trainable)\n",
    "\n",
    "    def forward(self, value=None):\n",
    "        if value is not None:\n",
    "            self.value = value\n",
    "\n",
    "    def backward(self):\n",
    "        self.gradients = {}\n",
    "        for n in self.outputs:\n",
    "            self.gradients[self] = n.gradients[self] * 1\n",
    "\n",
    "\n",
    "# 线性层节点\n",
    "class Linear(Node):\n",
    "    def __init__(self, inputs, name=None, is_trainable=False):\n",
    "        assert len(inputs) == 3, 'Please input [x,k,b]'\n",
    "\n",
    "        x, k, b = inputs\n",
    "        Node.__init__(self, [x, k, b], name=name, is_trainable=is_trainable)\n",
    "\n",
    "    def forward(self):\n",
    "        x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]\n",
    "        self.value = k.value * x.value + b.value\n",
    "\n",
    "    def backward(self):\n",
    "        x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]\n",
    "\n",
    "        for n in self.outputs:\n",
    "            grad_cost = n.gradients[self]\n",
    "\n",
    "            self.gradients[k] = grad_cost * x.value\n",
    "            self.gradients[x] = grad_cost * k.value\n",
    "            self.gradients[b] = grad_cost * 1\n",
    "\n",
    "            \n",
    "class Relu(Node):\n",
    "    def __init__(self, x, name=None, is_trainable=False):\n",
    "        Node.__init__(self, [x], name=name, is_trainable=is_trainable)\n",
    "        self.x = x\n",
    "\n",
    "    def forward(self):\n",
    "        self.value = self.x.value * (self.x.value > 0)\n",
    "\n",
    "    def backward(self):\n",
    "        for n in self.outputs:\n",
    "            grad_cost = n.gradients[self]\n",
    "            self.gradients[self.x] = grad_cost * (self.x.value > 0)\n",
    "\n",
    "\n",
    "class Sigmoid(Node):\n",
    "    def __init__(self, x, name=None, is_trainable=False):\n",
    "        Node.__init__(self, [x], name=name, is_trainable=is_trainable)\n",
    "        self.x = self.inputs[0]\n",
    "\n",
    "    def _sigmoid(self, x):\n",
    "        return 1. / (1 + np.exp(-1 * x))\n",
    "\n",
    "    def forward(self):\n",
    "        self.value = self._sigmoid(self.x.value)\n",
    "\n",
    "    def partial(self):\n",
    "        return self._sigmoid(self.x.value) * (1 - self._sigmoid(self.x.value))\n",
    "\n",
    "    def backward(self):\n",
    "        self.gradients[self.x] = 0\n",
    "\n",
    "        for n in self.outputs:\n",
    "            grad_cost = n.gradients[self]\n",
    "            self.gradients[self.x] += grad_cost * self.partial()\n",
    "            \n",
    "            \n",
    "# 均方误差\n",
    "class L2Loss(Node):\n",
    "    def __init__(self, y, y_hat, name=None, is_trainable=False):\n",
    "        Node.__init__(self, [y, y_hat], name=name, is_trainable=is_trainable)\n",
    "        self.y = y\n",
    "        self.y_hat = y_hat\n",
    "\n",
    "    def forward(self):\n",
    "        y_v = np.array(self.y.value)\n",
    "        y_hat_v = np.array(self.y_hat.value)\n",
    "        # 均方误差\n",
    "        self.value = np.mean((y_v - y_hat_v) ** 2)\n",
    "\n",
    "    def backward(self):\n",
    "        # 1/n sum (y- y_hat)**2\n",
    "        y_v = np.array(self.y.value)\n",
    "        y_hat_v = np.array(self.y_hat.value)\n",
    "\n",
    "        # 对应求导\n",
    "        self.gradients[self.y] = 2 * np.mean((y_v - y_hat_v))\n",
    "        self.gradients[self.y_hat] = -2 * np.mean((y_v - y_hat_v))\n",
    "\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 打印节点信息\n",
    "def node_info(node, info=None):\n",
    "    if info is not None:\n",
    "        print(info)\n",
    "    print('Node:{}  trainable:{}  value:{}  gradients:{}  input:{}  output:{}\\n'.format(\n",
    "        node, node.is_trainable, node.value, node.gradients, node.inputs, node.outputs))\n",
    "    \n",
    "# 打印模型信息\n",
    "def model_info(value_graph):\n",
    "    print('-'*80)\n",
    "    print('Graph： ', value_graph, '\\n\\n')\n",
    "\n",
    "    for node_i in value_graph:\n",
    "        node_info(node_i)\n",
    "\n",
    "    graph = nx.DiGraph(value_graph)\n",
    "    layout = nx.layout.spring_layout(graph)\n",
    "    nx.draw(nx.DiGraph(value_graph), layout, with_labels=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取计算图\n",
    "def get_graph(node, graph):\n",
    "    for node_input in node.inputs:\n",
    "        if node_input not in graph:\n",
    "            graph[node_input] = [node]\n",
    "        else:\n",
    "            graph[node_input].append(node)\n",
    "\n",
    "        # 若不是 Placeholder 则入度不为0，继续查找其输入节点\n",
    "        if not isinstance(node_input, Placeholder):\n",
    "            get_graph(node_input, graph)\n",
    "            \n",
    "            \n",
    "# 拓扑排序，根据计算图排序节点计算顺序\n",
    "def toplogic(graph):\n",
    "    sorted_node = []\n",
    "\n",
    "    while len(graph) > 0:\n",
    "\n",
    "        all_inputs = []\n",
    "        all_outputs = []\n",
    "\n",
    "        for n in graph:\n",
    "            all_inputs += graph[n]\n",
    "            all_outputs.append(n)\n",
    "\n",
    "        all_inputs = set(all_inputs)\n",
    "        all_outputs = set(all_outputs)\n",
    "\n",
    "        need_remove = all_outputs - all_inputs  # which in all_inputs but not in all_outputs\n",
    "\n",
    "        if len(need_remove) > 0:\n",
    "            node = random.choice(list(need_remove))\n",
    "\n",
    "            need_to_visited = [node]\n",
    "\n",
    "            if len(graph) == 1: need_to_visited += graph[node]\n",
    "\n",
    "            graph.pop(node)\n",
    "            sorted_node += need_to_visited\n",
    "\n",
    "            for _, links in graph.items():\n",
    "                if node in links: links.remove(node)\n",
    "        else:  # have cycle\n",
    "            break\n",
    "\n",
    "    return sorted_node\n",
    "\n",
    "\n",
    "# 前向、后向 计算\n",
    "def forward_and_backward(graph_order, monitor=False):\n",
    "    # 整体的参数就更新了一次\n",
    "    for node in graph_order:\n",
    "        if monitor:\n",
    "            print('forward computing node: {}'.format(node))\n",
    "        node.forward()\n",
    "\n",
    "    for node in graph_order[::-1]:\n",
    "        if monitor:\n",
    "            print('backward computing node: {}'.format(node))\n",
    "        node.backward()\n",
    "        \n",
    "        \n",
    "# 优化器\n",
    "def optimizer(graph, learning_rate=1e-2):\n",
    "    for t in graph:\n",
    "        if t.is_trainable:\n",
    "            t.value += -1 * t.gradients[t] * learning_rate\n",
    "            "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "## 创建模型过程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "创建后：\n",
      "Node:x  trainable:False  value:None  gradients:{}  input:[]  output:[]\n",
      "\n",
      "加入操作：\n",
      "Node:x  trainable:False  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "加入损失函数：\n",
      "Node:x  trainable:False  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "\n",
      "Node:y_hat  trainable:False  value:None  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "\n",
      "Node:loss  trainable:False  value:None  gradients:{}  input:[y, y_hat]  output:[]\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "Graph：  {y: [loss], y_hat: [loss], x: [y_hat], k: [y_hat], b: [y_hat]} \n",
      "\n",
      "\n",
      "Node:y  trainable:False  value:None  gradients:{}  input:[]  output:[loss]\n",
      "\n",
      "Node:y_hat  trainable:False  value:None  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "Node:x  trainable:False  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "Node:k  trainable:True  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "Node:b  trainable:True  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 数据节点\n",
    "_x = Placeholder(name='x')\n",
    "_y = Placeholder(name='y')\n",
    "\n",
    "_k = Placeholder(name='k', is_trainable=True)\n",
    "_b = Placeholder(name='b', is_trainable=True)\n",
    "\n",
    "# 节点信息\n",
    "node_info(_x, '创建后：')\n",
    "\n",
    "# 操作节点\n",
    "_y_hat = Linear([_x, _k, _b], name='y_hat')\n",
    "# 节点信息\n",
    "node_info(_x, '加入操作：')\n",
    "\n",
    "# 损失函数\n",
    "cost = L2Loss(_y, _y_hat, name='loss')\n",
    "# 节点信息\n",
    "node_info(_x, '加入损失函数：')\n",
    "node_info(_y_hat, '')\n",
    "node_info(cost, '')\n",
    "\n",
    "#  获取计算图\n",
    "value_graph = {}\n",
    "get_graph(cost, value_graph)\n",
    "    \n",
    "# 打印模型信息\n",
    "model_info(value_graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 前向计算\n",
    "按入度顺序依次计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "前向计算：\n",
      "Node:x  trainable:False  value:[1 2 3 4 5]  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "前向计算前：\n",
      "Node:y_hat  trainable:False  value:None  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "前向计算后：\n",
      "Node:y_hat  trainable:False  value:[10.9 20.9 30.9 40.9 50.9]  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "_x.forward(np.array([1,2,3,4,5]))\n",
    "_y.forward(np.array([0,0,0,0,0]))\n",
    "_k.forward(10)\n",
    "_b.forward(0.9)\n",
    "\n",
    "# 节点信息\n",
    "node_info(_x, '前向计算：')\n",
    "\n",
    "node_info(_y_hat, '前向计算前：')\n",
    "\n",
    "_y_hat.forward()\n",
    "\n",
    "node_info(_y_hat, '前向计算后：')\n",
    "\n",
    "cost.forward()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 后向计算\n",
    "逆向顺序 计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss 后向计算前：\n",
      "Node:y_hat  trainable:False  value:[10.9 20.9 30.9 40.9 50.9]  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "loss 后向计算前：\n",
      "Node:loss  trainable:False  value:1154.81  gradients:{}  input:[y, y_hat]  output:[]\n",
      "\n",
      "loss 后向计算后：\n",
      "Node:y_hat  trainable:False  value:[10.9 20.9 30.9 40.9 50.9]  gradients:{}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "loss 后向计算后：\n",
      "Node:loss  trainable:False  value:1154.81  gradients:{y: -61.8, y_hat: 61.8}  input:[y, y_hat]  output:[]\n",
      "\n",
      "y_hat 后向计算后：\n",
      "Node:y_hat  trainable:False  value:[10.9 20.9 30.9 40.9 50.9]  gradients:{k: array([ 61.8, 123.6, 185.4, 247.2, 309. ]), x: 618.0, b: 61.8}  input:[x, k, b]  output:[loss]\n",
      "\n",
      "x 后向计算前：\n",
      "Node:x  trainable:False  value:[1 2 3 4 5]  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "x 后向计算后：\n",
      "Node:x  trainable:False  value:[1 2 3 4 5]  gradients:{x: 618.0}  input:[]  output:[y_hat]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "node_info(_y_hat, 'loss 后向计算前：')\n",
    "node_info(cost, 'loss 后向计算前：')\n",
    "\n",
    "cost.backward()\n",
    "\n",
    "node_info(_y_hat, 'loss 后向计算后：')\n",
    "node_info(cost, 'loss 后向计算后：')\n",
    "\n",
    "_y_hat.backward()\n",
    "\n",
    "node_info(_y_hat, 'y_hat 后向计算后：')\n",
    "node_info(_x, 'x 后向计算前：')\n",
    "_x.backward()\n",
    "node_info(_x, 'x 后向计算后：')\n",
    "\n",
    "_k.backward()\n",
    "_b.backward()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "节点计算顺序：  [x, y, k, b, y_hat, loss]\n"
     ]
    }
   ],
   "source": [
    "# 获取计算图\n",
    "value_graph = {}\n",
    "get_graph(cost, value_graph)\n",
    "\n",
    "# 拓扑排序\n",
    "visitor_order = toplogic(value_graph)\n",
    "print('节点计算顺序： ', visitor_order)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Node:b  trainable:True  value:0.9  gradients:{b: 61.8}  input:[]  output:[y_hat]\n",
      "\n",
      "Node:b  trainable:True  value:0.9  gradients:{b: 61.8}  input:[]  output:[y_hat]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "node_info(_b)\n",
    "# 权重更新\n",
    "optimizer(value_graph, learning_rate=1)\n",
    "node_info(_b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "## 完整训练过程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "Graph：  {y: [loss], y_hat: [loss], output2: [y_hat], output1: [output2], x: [output1], w1: [output1], b1: [output1], w2: [y_hat], b2: [y_hat]} \n",
      "\n",
      "\n",
      "Node:y  trainable:False  value:None  gradients:{}  input:[]  output:[loss]\n",
      "\n",
      "Node:y_hat  trainable:False  value:None  gradients:{}  input:[output2, w2, b2]  output:[loss]\n",
      "\n",
      "Node:output2  trainable:False  value:None  gradients:{}  input:[output1]  output:[y_hat]\n",
      "\n",
      "Node:output1  trainable:False  value:None  gradients:{}  input:[x, w1, b1]  output:[output2]\n",
      "\n",
      "Node:x  trainable:False  value:None  gradients:{}  input:[]  output:[output1]\n",
      "\n",
      "Node:w1  trainable:True  value:None  gradients:{}  input:[]  output:[output1]\n",
      "\n",
      "Node:b1  trainable:True  value:None  gradients:{}  input:[]  output:[output1]\n",
      "\n",
      "Node:w2  trainable:True  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n",
      "Node:b2  trainable:True  value:None  gradients:{}  input:[]  output:[y_hat]\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 创建模型\n",
    "x, y = Placeholder(name='x'), Placeholder(name='y')\n",
    "w1, b1 = Placeholder(name='w1', is_trainable=True), Placeholder(name='b1', is_trainable=True)\n",
    "w2, b2 = Placeholder(name='w2', is_trainable=True), Placeholder(name='b2', is_trainable=True)\n",
    "# w3, b3 = Placeholder(name='w3', is_trainable=True), Placeholder(name='b3', is_trainable=True)\n",
    "\n",
    "output1 = Linear([x, w1, b1], name='output1')\n",
    "output2 = Relu(output1, name='output2')\n",
    "y_hat = Linear([output2, w2, b2], name='y_hat')\n",
    "\n",
    "# 损失函数\n",
    "cost = L2Loss(y, y_hat, name='loss')\n",
    "\n",
    "#  获取计算图\n",
    "value_graph = {}\n",
    "get_graph(cost, value_graph)\n",
    "    \n",
    "# 打印模型信息\n",
    "model_info(value_graph)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "X shape:(506,)  Y shape:(506,)\n"
     ]
    }
   ],
   "source": [
    "# 训练数据\n",
    "from sklearn.datasets import load_boston\n",
    "data = load_boston()\n",
    "train_data_x, train_data_y = data['data'][:, 5], data['target']\n",
    "print('X shape:{}  Y shape:{}'.format(train_data_x.shape, train_data_y.shape))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 初始化权重\n",
    "w1_, b1_ = np.random.normal(), np.random.normal()\n",
    "w2_, b2_ = np.random.normal(), np.random.normal()\n",
    "# w3_, b3_ = np.random.normal(), np.random.normal()\n",
    "\n",
    "feed_dict = {\n",
    "    x: train_data_x,\n",
    "    y: train_data_y,\n",
    "    w1: w1_,\n",
    "    w2: w2_,\n",
    "    b1: b1_,\n",
    "    b2: b2_,\n",
    "}\n",
    "\n",
    "# 权重赋值\n",
    "for node_i in value_graph:\n",
    "    if isinstance(node_i, Placeholder) and node_i.is_trainable:\n",
    "        node_i.value = feed_dict[node_i]\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "节点计算顺序：  [w2, w1, b1, x, y, b2, output1, output2, y_hat, loss]\n"
     ]
    }
   ],
   "source": [
    "# 节点计算顺序   拓扑排序\n",
    "graph_sort = toplogic(value_graph)\n",
    "print('节点计算顺序： ', graph_sort)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[297.7915334840961, 103.53429955972518, 97.07192211419317, 98.0119338940923, 93.725423598783, 83.43189069011986, 69.10422828891622, 98.35852182053907, 88.05140898358255, 77.52427671568115, 78.95574485182837, 85.92766780911721, 84.66457539508772, 75.21015871032573, 70.30571697741124, 76.96325807363334, 86.41293676946961, 82.60238236011192, 94.58226801090613, 89.31359706298873, 87.64677027297492, 89.84480162967677, 88.05571268667622, 100.37491470101315, 83.94037138738368, 78.24114708459513, 90.28370501287388, 84.04685649722875, 82.90394448436028, 93.71295279558021, 72.42555956402302, 78.44682666618634, 86.02840028484829, 92.94638278700229, 82.74947465048794, 92.82071371343747, 73.15262231808568, 83.78116654778873, 73.97072010224457, 86.16516893640264, 94.13438756838744, 78.90531938961495, 84.03440367437496, 85.3051999549493, 87.39583226150104, 76.09886645265348, 83.16804441231835, 96.3283681954453, 86.85690441856555, 88.21588477400742, 91.3988236678291, 79.3817345411218, 83.65807078439195, 95.01360500462354, 90.62416181059076, 84.38672398928001, 83.62736595991151, 80.66542449351323, 77.0267879890394, 84.48952582738933, 87.0797334151798, 86.40183735249607, 82.92315527739328, 90.05820210639033, 91.18937598419957, 81.82454766834176, 88.28835277870725, 94.81217831289904, 92.54095728396727, 91.17331807261874, 85.09688208067541, 86.24182062337309, 80.43982188222252, 87.08704784560535, 89.63795340758973, 76.72031672815406, 76.36930658984218, 87.33444059223484, 80.80815076719523, 81.18440618310558, 76.62929497246267, 89.81810026001743, 74.46302699463037, 95.44270170152153, 78.66224041847947, 92.35285939455508, 85.07164131501773, 88.35282893919351, 89.51218245239056, 85.54488800940463, 88.06830012168179, 75.64009124219639, 77.40938090265621, 96.97385199835455, 88.20241096001577, 81.03009205779085, 91.57144488879969, 88.10172765798187, 79.95079447576764, 86.46720318753603]\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "epoch = 100\n",
    "\n",
    "learning_rate = 1e-3\n",
    "batch_num = 500\n",
    "\n",
    "losses = []\n",
    "\n",
    "for e in range(epoch):\n",
    "\n",
    "    batch_loss = 0\n",
    "\n",
    "    for b in range(batch_num):\n",
    "        index = np.random.choice(range(len(train_data_x)))\n",
    "        x.value = train_data_x[index]\n",
    "        y.value = train_data_y[index]\n",
    "        \n",
    "        # 计算前向、后向\n",
    "        forward_and_backward(graph_sort, monitor=False)\n",
    "\n",
    "        # 权重更新\n",
    "        optimizer(graph_sort, learning_rate=learning_rate)\n",
    "        # sgd stocastic gradient descent\n",
    "\n",
    "        batch_loss += cost.value\n",
    "\n",
    "    losses.append(batch_loss / batch_num)\n",
    "\n",
    "print(losses)\n",
    "\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] =False\n",
    "\n",
    "plt.plot(losses)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
